#pragma once

#include "evnet/http/http.hpp"
#include "evnet/buffer.hpp"

#include <string>

namespace evnet {
namespace http {

enum status_code : evhtp_res
{
    processing = 102,
    ok = 200,
    created = 201,
    not_modified = 304,
    see_other = 303,
    bad_request = 400,
    unauthorized = 401,
    parameter_required = 402,
    forbidden = 403,
    not_found = 404,
    request_timeout = 408,
    conflict = 409,
    internal_error = 500,
    not_implemented = 501,
    bad_gateway = 502
};

class basic_reply
{
protected:

    evhtp_res code_{ 0 };

public:

    basic_reply() = default;
    basic_reply(const basic_reply&) = delete;

    basic_reply(basic_reply&& other) throw()
        : code_(std::move(other.code_))
    {   }

    basic_reply(evhtp_res code) throw()
        : code_(code)
    {   }

    virtual ~basic_reply() throw()
    {   }

    evhtp_res get_code() const throw()
    {
        return code_;
    }

    bool equal(status_code code) const throw()
    {
        return code_ == code;
    }

    bool is_ok() const throw()
    {
        return code_ < 400;
    }

    bool operator==(status_code code) const throw()
    {
        return equal(code);
    }

    bool operator==(const basic_reply& e) const throw()
    {
        return code_ == e.code_;
    }

    bool operator!=(const basic_reply& e) const throw()
    {
        return code_ != e.code_;
    }

    template<typename T>
    void operator()(T& request) const throw()
    {
        send(request);
    }

protected:

    template<typename T>
    void send(T& request) const throw()
    {
        evhtp_send_reply(request.handle(), code_);
    }
};

class string_reply
    : public basic_reply
{
    std::string text_;

public:

    string_reply() = default;
    string_reply(const string_reply&) = delete;

    string_reply(string_reply&& other) throw()
        : basic_reply(std::move(other))
        , text_(std::move(other.text_))
    {   }

    string_reply(evhtp_res code, std::string text) throw()
        : basic_reply(code)
        , text_(std::move(text))
    {   }

    template<typename T>
    void operator()(T& request) const
    {
        int result = evbuffer_add(request.buffer_out(),
                                  text_.c_str(), text_.size());
        if (result == -1)
            throw std::runtime_error("evbuffer_add");

        send(request);
    }
};

class cstr_reply
    : public basic_reply
{
    const char* text_{nullptr};
    std::size_t length_{0};

public:

    cstr_reply() = default;

    cstr_reply(const cstr_reply&) = delete;
    cstr_reply& operator=(const cstr_reply&) = delete;

    cstr_reply(cstr_reply&& other)
        : basic_reply(std::move(other))
    {
        std::swap(text_, other.text_);
        std::swap(length_, other.length_);
    }

    cstr_reply(evhtp_res code, const char* text, std::size_t length)
        : basic_reply(code)
        , text_(text)
        , length_(length)
    {
        if (length && !text_)
            throw std::runtime_error("cstr_reply");
    }

    cstr_reply(evhtp_res code, const char* text)
        : basic_reply(code)
        , text_(text)
        , length_((text_) ? strlen(text_) : 0)
    {
        if (!text_)
            throw std::runtime_error("cstr_reply");
    }

    template<typename T>
    void operator()(T& request) const
    {
        int result = evbuffer_add(request.buffer_out(), text_, length_);
        if (result == -1)
            throw std::runtime_error("evbuffer_add");

        send(request);
    }

};

class buffer_reply
    : public basic_reply
{
    buffer buffer_;

public:

    buffer_reply() = default;

    buffer_reply(const buffer_reply&) = delete;

    buffer_reply(buffer_reply&& other) throw()
        : basic_reply(std::move(other))
        , buffer_(std::move(other.buffer_))
    {   }

    buffer_reply(evhtp_res code, buffer buf) throw()
        : basic_reply(code)
        , buffer_(std::move(buf))
    {   }

    template<typename T>
    void operator()(T& request) const
    {
        int result = evbuffer_add_buffer(request.buffer_out(),
                                         buffer_.handle());
        if (result == -1)
            throw std::runtime_error("evbuffer_add_buffer");

        send(request);
    }
};

class reply
{
public:

    static basic_reply processing() throw()
    {
        return basic_reply(102);
    }

    static buffer_reply processing(buffer buf) throw()
    {
        return buffer_reply(102, std::move(buf));
    }

    static string_reply processing(std::string text) throw()
    {
        return string_reply(102, std::move(text));
    }

    static cstr_reply processing(const char* text)
    {
        return cstr_reply(102, text);
    }

    static cstr_reply processing(const char* text, std::size_t length)
    {
        return cstr_reply(102, text, length);
    }

    static basic_reply ok() throw()
    {
        return basic_reply(200);
    }

    static buffer_reply ok(buffer buf) throw()
    {
        return buffer_reply(200, std::move(buf));
    }

    static string_reply ok(std::string text) throw()
    {
        return string_reply(200, std::move(text));
    }

    static cstr_reply ok(const char* text)
    {
        return cstr_reply(200, text);
    }

    static cstr_reply ok(const char* text, std::size_t length)
    {
        return cstr_reply(200, text, length);
    }

    static basic_reply created() throw()
    {
        return basic_reply(201);
    }

    static buffer_reply created(buffer buf) throw()
    {
        return buffer_reply(201, std::move(buf));
    }

    static string_reply created(std::string text) throw()
    {
        return string_reply(201, std::move(text));
    }

    static cstr_reply created(const char* text)
    {
        return cstr_reply(201, text);
    }

    static cstr_reply created(const char* text, std::size_t length)
    {
        return cstr_reply(201, text, length);
    }

    static basic_reply see_other() throw()
    {
        return basic_reply(303);
    }

    static buffer_reply see_other(buffer buf) throw()
    {
        return buffer_reply(303, std::move(buf));
    }

    static string_reply see_other(std::string text) throw()
    {
        return string_reply(303, std::move(text));
    }

    static cstr_reply see_other(const char* text)
    {
        return cstr_reply(303, text);
    }

    static cstr_reply see_other(const char* text, std::size_t length)
    {
        return cstr_reply(303, text, length);
    }

    static basic_reply not_modified() throw()
    {
        return basic_reply(304);
    }

    static buffer_reply not_modified(buffer buf) throw()
    {
        return buffer_reply(304, std::move(buf));
    }

    static string_reply not_modified(std::string text) throw()
    {
        return string_reply(304, std::move(text));
    }

    static cstr_reply not_modified(const char* text)
    {
        return cstr_reply(304, text);
    }

    static cstr_reply not_modified(const char* text, std::size_t length)
    {
        return cstr_reply(304, text, length);
    }

    static basic_reply bad_request() throw()
    {
        return basic_reply(400);
    }

    static buffer_reply bad_request(buffer buf) throw()
    {
        return buffer_reply(400, std::move(buf));
    }

    static string_reply bad_request(const std::string& text) throw()
    {
        return string_reply(400, std::move(text));
    }

    static cstr_reply bad_request(const char* text)
    {
        return cstr_reply(400, text);
    }

    static cstr_reply bad_request(const char* text, std::size_t length)
    {
        return cstr_reply(400, text, length);
    }

    static basic_reply unauthorized() throw()
    {
        return basic_reply(401);
    }

    static buffer_reply unauthorized(buffer buf) throw()
    {
        return buffer_reply(401, std::move(buf));
    }

    static string_reply unauthorized(const std::string& text) throw()
    {
        return string_reply(401, std::move(text));
    }

    static cstr_reply unauthorized(const char* text)
    {
        return cstr_reply(401, text);
    }

    static cstr_reply unauthorized(const char* text, std::size_t length)
    {
        return cstr_reply(401, text, length);
    }

    static basic_reply parameter_required() throw()
    {
        return basic_reply(402);
    }

    static buffer_reply parameter_required(buffer buf) throw()
    {
        return buffer_reply(402, std::move(buf));
    }

    static string_reply parameter_required(const std::string& text) throw()
    {
        return string_reply(402, std::move(text));
    }

    static cstr_reply parameter_required(const char* text)
    {
        return cstr_reply(402, text);
    }

    static cstr_reply parameter_required(const char* text, std::size_t length)
    {
        return cstr_reply(402, text, length);
    }

    static basic_reply forbidden() throw()
    {
        return basic_reply(403);
    }

    static buffer_reply forbidden(buffer buf) throw()
    {
        return buffer_reply(403, std::move(buf));
    }

    static string_reply forbidden(const std::string& text) throw()
    {
        return string_reply(403, std::move(text));
    }

    static cstr_reply forbidden(const char* text)
    {
        return cstr_reply(403, text);
    }

    static cstr_reply forbidden(const char* text, std::size_t length)
    {
        return cstr_reply(403, text, length);
    }

    static basic_reply not_found() throw()
    {
        return basic_reply(404);
    }

    static buffer_reply not_found(buffer buf) throw()
    {
        return buffer_reply(404, std::move(buf));
    }

    static string_reply not_found(const std::string& text) throw()
    {
        return string_reply(404, std::move(text));
    }

    static cstr_reply not_found(const char* text)
    {
        return cstr_reply(404, text);
    }

    static cstr_reply not_found(const char* text, std::size_t length)
    {
        return cstr_reply(404, text, length);
    }

    static basic_reply request_timeout() throw()
    {
        return basic_reply(408);
    }

    static buffer_reply request_timeout(buffer buf) throw()
    {
        return buffer_reply(408, std::move(buf));
    }

    static string_reply request_timeout(const std::string& text) throw()
    {
        return string_reply(408, std::move(text));
    }

    static cstr_reply request_timeout(const char* text)
    {
        return cstr_reply(408, text);
    }

    static cstr_reply request_timeout(const char* text, std::size_t length)
    {
        return cstr_reply(408, text, length);
    }

    static basic_reply conflict() throw()
    {
        return basic_reply(409);
    }

    static buffer_reply conflict(buffer buf) throw()
    {
        return buffer_reply(409, std::move(buf));
    }

    static string_reply conflict(const std::string& text) throw()
    {
        return string_reply(409, std::move(text));
    }

    static cstr_reply conflict(const char* text)
    {
        return cstr_reply(409, text);
    }

    static cstr_reply conflict(const char* text, std::size_t length)
    {
        return cstr_reply(409, text, length);
    }

    static basic_reply internal_error() throw()
    {
        return basic_reply(500);
    }

    static buffer_reply internal_error(buffer buf) throw()
    {
        return buffer_reply(500, std::move(buf));
    }

    static string_reply internal_error(std::string text) throw()
    {
        return string_reply(500, std::move(text));
    }

    static cstr_reply internal_error(const char* text)
    {
        return cstr_reply(500, text);
    }

    static cstr_reply internal_error(const char* text, std::size_t length)
    {
        return cstr_reply(500, text, length);
    }

    static basic_reply not_implemented() throw()
    {
        return basic_reply(501);
    }

    static buffer_reply not_implemented(buffer buf) throw()
    {
        return buffer_reply(501, std::move(buf));
    }

    static string_reply not_implemented(std::string text) throw()
    {
        return string_reply(501, std::move(text));
    }

    static cstr_reply not_implemented(const char* text)
    {
        return cstr_reply(501, text);
    }

    static cstr_reply not_implemented(const char* text, std::size_t length)
    {
        return cstr_reply(501, text, length);
    }

    static basic_reply bad_gateway() throw()
    {
        return basic_reply(502);
    }

    static buffer_reply bad_gateway(buffer buf) throw()
    {
        return buffer_reply(502, std::move(buf));
    }

    static string_reply bad_gateway(std::string text) throw()
    {
        return string_reply(502, std::move(text));
    }

    static cstr_reply bad_gateway(const char* text)
    {
        return cstr_reply(502, text);
    }

    static cstr_reply bad_gateway(const char* text, std::size_t length)
    {
        return cstr_reply(502, text, length);
    }

    static basic_reply gateway_timeout() throw()
    {
        return basic_reply(504);
    }

    static buffer_reply gateway_timeout(buffer buf) throw()
    {
        return buffer_reply(504, std::move(buf));
    }

    static string_reply gateway_timeout(std::string text) throw()
    {
        return string_reply(504, std::move(text));
    }

    static cstr_reply gateway_timeout(const char* text)
    {
        return cstr_reply(504, text);
    }

    static cstr_reply gateway_timeout(const char* text, std::size_t length)
    {
        return cstr_reply(504, text, length);
    }
};

} // namespace http
} // namespace evnet
